/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.beans;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;

import java.lang.reflect.Method;

import java.util.List;
import java.util.ArrayList;

import com.sun.beans.ObjectHandler;

/**
 * A MethodDescriptor describes a particular method that a Java Bean
 * supports for external access from other components.
 */

public class MethodDescriptor extends FeatureDescriptor {

    private Reference methodRef;

    private String[] paramNames;

    private List params;

    private ParameterDescriptor parameterDescriptors[];

    /**
     * Constructs a <code>MethodDescriptor</code> from a
     * <code>Method</code>.
     *
     * @param method    The low-level method information.
     */
    public MethodDescriptor(Method method) {
	this(method, null);
    }


    /**
     * Constructs a <code>MethodDescriptor</code> from a
     * <code>Method</code> providing descriptive information for each
     * of the method's parameters.
     *
     * @param method    The low-level method information.
     * @param parameterDescriptors  Descriptive information for each of the
     *		 		method's parameters.
     */
    public MethodDescriptor(Method method, 
		ParameterDescriptor parameterDescriptors[]) {
	setName(method.getName());
	setMethod(method);
	this.parameterDescriptors = parameterDescriptors;
    }

    /**
     * Gets the method that this MethodDescriptor encapsualtes. 
     *
     * @return The low-level description of the method
     */
    public synchronized Method getMethod() {
	Method method = getMethod0();
	if (method == null) {
	    Class cls = getClass0();
	    if (cls != null) {
		Class[] params = getParams();
		if (params == null) {
		    for (int i = 0; i < 3; i++) {
			// Find methods for up to 2 params. We are guessing here.
			// This block should never execute unless the classloader
			// that loaded the argument classes disappears.
			method = Introspector.findMethod(cls, getName(), i, null);
			if (method != null) {
			    break;
			}
		    }
		} else {
		    method = Introspector.findMethod(cls, getName(), 
						     params.length, params);
		}
		setMethod(method);
	    }
	}
	return method;
    }

    private synchronized void setMethod(Method method) {
	if (method == null) {
	    return;
	}
	if (getClass0() == null) {
	    setClass0(method.getDeclaringClass());
	}
	setParams(method.getParameterTypes());
	methodRef = createReference(method, true);
    }

    private Method getMethod0() {
	return (Method)getObject(methodRef);
    }

    private synchronized void setParams(Class[] param) {
	if (param == null) {
	    return;
	}
	paramNames = new String[param.length];
	params = new ArrayList(param.length);
	for (int i = 0; i < param.length; i++) {
	    paramNames[i] = param[i].getName();
	    params.add(new WeakReference(param[i]));
	}
    }

    // pp getParamNames used as an optimization to avoid method.getParameterTypes.
    String[] getParamNames() {
	return paramNames;
    }

    private synchronized Class[] getParams() {
	Class[] clss = new Class[params.size()];

	for (int i = 0; i < params.size(); i++) {
	    Reference ref = (Reference)params.get(i);
	    Class cls = (Class)ref.get();
	    if (cls == null) {
		return null;
	    } else {
		clss[i] = cls;
	    }
	}
	return clss;
    }

    /**
     * Gets the ParameterDescriptor for each of this MethodDescriptor's
     * method's parameters.
     *
     * @return The locale-independent names of the parameters.  May return
     *		a null array if the parameter names aren't known.
     */
    public ParameterDescriptor[] getParameterDescriptors() {
	return parameterDescriptors;
    }

    /*
    public String toString() {
	String message = "name=" + getName();
	Class cls = getClass0();
	if (cls != null) {
	    message += ", class=";
	    message += cls.getName();
	}
	String[] names = getParamNames();
	if (names != null) {
	    for (int i = 0; i < names.length; i++) {
		message += ", param=" + names[i];
	    }
	}
	return message;
	} */

    /*
     * Package-private constructor
     * Merge two method descriptors.  Where they conflict, give the
     * second argument (y) priority over the first argument (x).
     * @param x  The first (lower priority) MethodDescriptor
     * @param y  The second (higher priority) MethodDescriptor
     */

    MethodDescriptor(MethodDescriptor x, MethodDescriptor y) {
	super(x,y);

	methodRef = x.methodRef;
	if (y.methodRef != null) {
	    methodRef = y.methodRef;
	}
	params = x.params;
	if (y.params != null) {
	    params = y.params;
	}
	paramNames = x.paramNames;
	if (y.paramNames != null) {
	    paramNames = y.paramNames;
	}

	parameterDescriptors = x.parameterDescriptors;
	if (y.parameterDescriptors != null) {
	    parameterDescriptors = y.parameterDescriptors;
	}
    }

    /*
     * Package-private dup constructor
     * This must isolate the new object from any changes to the old object.
     */
    MethodDescriptor(MethodDescriptor old) {
	super(old);

	methodRef = old.methodRef;
	params = old.params;
	paramNames = old.paramNames;

	if (old.parameterDescriptors != null) {
	    int len = old.parameterDescriptors.length;
	    parameterDescriptors = new ParameterDescriptor[len];
	    for (int i = 0; i < len ; i++) {
	        parameterDescriptors[i] = new ParameterDescriptor(old.parameterDescriptors[i]);
	    }
	}
    }

}
